/* $FreeBSD$ */
/*-
 * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
 * Copyright (c) <2014-2015>, <Huawei Technologies Co., Ltd>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 *---------------------------------------------------------------------------*/

#include <linux/module.h>
#include "linux/device.h"
#include "linux/platform_device.h"
#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#include "controller/usb_host/ehci.h"
#include "controller/usb_host/ehcireg.h"
#endif

/*lint -esym(459, hiehci_driver)*/

static int hiehci_remove(struct platform_device *pdev);

static void hiehci_post_reset(struct ehci_softc *sc)
{
    uint32_t usbmode;

    /* Force HOST mode */
    usbmode = EOREAD4(sc, EHCI_USBMODE_NOLPM);
    usbmode &= ~EHCI_UM_CM;
    usbmode |= EHCI_UM_CM_HOST;
    EOWRITE4(sc, EHCI_USBMODE_NOLPM, usbmode);
}

static int hiehci_probe(struct platform_device *pdev)
{
    device_t dev;
    device_t newdev;
    struct resource *res;
    ehci_softc_t *pst_sc = NULL;
    int err = ENXIO;

    dev = pdev->dev.p;
    if (dev != NULL) {
        return 0;
    }

    dev = usbmalloc(sizeof(*dev),
        M_DEVBUF, M_WAITOK | M_ZERO);

    if (dev == NULL) {
        dprintf("hi_ehci root device add fail\n");
        goto error;
    }

    TAILQ_INIT(&dev->dev_children);

    pdev->dev.p = dev;

    device_set_desc(dev, "hiusb-ehci");
    (void)snprintf_s(dev->dev_nameunit, sizeof(dev->dev_nameunit), sizeof(dev->dev_nameunit) - 1, "%s", "EHCI");

    pst_sc = usbmalloc(sizeof(struct ehci_softc),
        M_DEVBUF, M_WAITOK | M_ZERO);

    if (pst_sc == NULL) {
        err = ENOMEM;
        goto error;
    }
    dev->dev_softc_alloc = 1;/*lint !e613*/

    /* initialise some bus fields */
    pst_sc->sc_bus.parent = dev;
    pst_sc->sc_bus.devices = pst_sc->sc_devices;
    pst_sc->sc_bus.devices_max = EHCI_MAX_DEVICES; /*lint !e506*/
    pst_sc->sc_bus.dma_parent_tag[0].dma_bits = 32;
    pst_sc->sc_bus.dma_bits = 32;

    /* get all DMA memory */
    if (usb_bus_mem_alloc_all(&pst_sc->sc_bus,
        USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc)) { /*lint !e546*/
        err = ENOMEM;
        goto error;
    }
    res = platform_get_resource(pdev,IORESOURCE_MEM,0);
    if (res == NULL) {
        goto error;
    }

    pst_sc->sc_io_res = platform_ioremap_resource(res);
    pst_sc->sc_io_tag = (void *)pst_sc->sc_io_res;
    pst_sc->sc_io_hdl = (bus_space_handle_t)pst_sc->sc_io_res;
    pst_sc->sc_io_size = CONFIG_HIUSB_EHCI_IOSIZE;

    pst_sc->sc_flags |= EHCI_SCFLG_DONTRESET;
    err = bus_request_intr(platform_get_irq(pdev, 0),0,(irq_handler_t)ehci_interrupt,pst_sc);
    if (err) {
        goto error;
    }

    /* Setup callbacks. */
    pst_sc->sc_vendor_post_reset = hiehci_post_reset;
    pst_sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc;

    dev->dev_sc = pst_sc;
    newdev = device_add_child(dev, "usbus", -1);
    if (newdev == NULL) {
        device_printf(dev, "Could not add USB device\n");
        goto error;
    }
    USB_BUS_LOCK(&pst_sc->sc_bus);
    pst_sc->sc_bus.bdev = newdev;
    device_set_ivars(pst_sc->sc_bus.bdev, &pst_sc->sc_bus);
    USB_BUS_UNLOCK(&pst_sc->sc_bus);

    hiusb_start_hcd();
    hiusb_device2host();

    err = ehci_init(pst_sc);
    if (!err) {
      err = device_probe_and_attach(pst_sc->sc_bus.bdev);
    }
    if (err) {
      device_printf(dev, "ehci init failed err=%d\n", err);
      goto error;
    }
    dev->dev_attached = 1;

    return (0);

error:
    device_printf(dev, "ehci attach failed err=%d\n", err);
    (void)hiehci_remove(pdev);

    return err;/*lint !e438*/
}


static int hiehci_suspend(struct device *pst_dev)
{
    device_t dev = pst_dev->p;
    ehci_softc_t *sc = (ehci_softc_t *)device_get_softc(dev);
    device_t bdev;
    if ((sc != NULL) && (sc->sc_bus.bdev != NULL)) {
       bdev = sc->sc_bus.bdev;
       return DEVICE_SUSPEND(bdev);/*lint !e611*/
    }
    return (0);
}
static int hiehci_resume(struct device *pst_dev)
{
    device_t dev = pst_dev->p;
    ehci_softc_t *sc = (ehci_softc_t *)device_get_softc(dev);
    device_t bdev;
    if ((sc != NULL) && (sc->sc_bus.bdev != NULL)) {
        bdev = sc->sc_bus.bdev;
        return DEVICE_RESUME(bdev);/*lint !e611*/
    }

    return 0;
}
static const struct dev_pm_op hiehci_dev_pm_ops = {
    .suspend = hiehci_suspend,
    .resume = hiehci_resume,
};
static int hiehci_remove(struct platform_device *pdev)
{
    device_t dev = pdev->dev.p;
    ehci_softc_t *pst_sc = NULL;
    device_t bdev;

    if(dev == NULL)
        return -1;
    pst_sc = (ehci_softc_t *)device_get_softc(dev);
    if(pst_sc == NULL)
        goto disable;

    if (pst_sc->sc_bus.bdev) {
        bdev = pst_sc->sc_bus.bdev;
        (void)device_detach(bdev);
        (void)device_delete_child(dev, bdev);
    }
    /* during module unload there are lots of children leftover */
    (void)device_delete_children(dev);

    (void)bus_release_intr(platform_get_irq(pdev, 0), pst_sc);
    usb_bus_mem_free_all(&pst_sc->sc_bus, &ehci_iterate_hw_softc); /*lint !e546*/
    hiusb_stop_hcd();
    if(pst_sc != NULL){
        free(pst_sc);
        ((device_t)(pdev->dev.p))->dev_sc = NULL;
    }
disable:
    if(dev){
        free(dev);
        pdev->dev.p = NULL;
    }
    return (0);/*lint !e438*/

}

static struct platform_driver hiehci_driver = {
    .probe      = hiehci_probe,
    .remove     = hiehci_remove,
    .driver     = {
        .name   = "hi_ehci",
        .pm = &hiehci_dev_pm_ops,
    },
};

int hiehci_init(void)
{
    return platform_driver_register(&hiehci_driver);
}

void hiehci_exit(void)
{
    (void)platform_driver_unregister(&hiehci_driver);
}

module_init(hiehci_init);
module_exit(hiehci_exit);/*lint !e19*/

